namespace BassettData.Robots
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// Reads a robot world.
    /// </summary>
    public class WorldReader
    {
        /// <summary>
        /// Reads the world from the given file.
        /// </summary>
        /// <param name="fileName">The file where the world is defined.</param>
        /// <remarks>
        /// The world file format is defined at http://csis.pace.edu/~bergin/KarelJava2ed/karelexperimental.html.
        /// </remarks>
        public void ReadFromFile(string fileName)
        {
            if (!File.Exists(fileName))
            {
                throw new ArgumentException(string.Format("File {0} does not exist.", fileName));
            }

            foreach (var line in File.ReadAllLines(fileName))
            {
                var lineToParse = line.ToLowerInvariant().Trim();

                if (string.IsNullOrEmpty(lineToParse))
                {
                    continue;
                }

                var parser = new ArgumentParser(lineToParse);

                switch (parser.Keyword)
                {
                    case "streets":
                        World.Instance.NumberOfStreets = parser.Argument(0);
                        break;

                    case "avenues":
                        World.Instance.NumberOfAvenues = parser.Argument(0);
                        break;

                    case "beepers":
                        AddBeepersToWorld(parser);
                        break;

                    case "eastwestwalls":
                        AddEastWestWallToWorld(parser);
                        break;

                    case "northsouthwalls":
                        AddNorthSouthWallToWorld(parser);
                        break;

                    default:

                        // don't care about keywords we haven't implemented yet
                        break;
                }
            }
        }

        /// <summary>
        /// Adds the beepers defined in the parser to the world.
        /// </summary>
        /// <param name="parser">The parser.</param>
        private static void AddBeepersToWorld(ArgumentParser parser)
        {
            Location beeperLocation = new Location(parser.Argument(0), parser.Argument(1));
            int numberOfBeepers = parser.Argument(2);
            World.Instance.BeeperLocations.Add(beeperLocation, numberOfBeepers);
        }

        /// <summary>
        /// Adds the east west wall defined in the parser to the world.
        /// </summary>
        /// <param name="parser">The parser.</param>
        private static void AddEastWestWallToWorld(ArgumentParser parser)
        {
            int street = parser.Argument(0);
            int firstAvenue = parser.Argument(1);
            int lastAvenue = parser.Argument(2);
            World.Instance.EastWestWalls.Add(new EastWestWallLocation(street, firstAvenue, lastAvenue));
        }

        /// <summary>
        /// Adds the north south wall defined in the parser to the world.
        /// </summary>
        /// <param name="parser">The parser.</param>
        private static void AddNorthSouthWallToWorld(ArgumentParser parser)
        {
            int avenue = parser.Argument(0);
            int firstStreet = parser.Argument(1);
            int lastStreet = parser.Argument(2);
            World.Instance.NorthSouthWalls.Add(new NorthSouthWallLocation(avenue, firstStreet, lastStreet));
        }

        /// <summary>
        /// Parses the arguments.
        /// </summary>
        private class ArgumentParser
        {
            /// <summary>
            /// The parsed arguments.
            /// </summary>
            private List<int> arguments = new List<int>();

            /// <summary>
            /// Initializes a new instance of the <see cref="ArgumentParser"/> class.
            /// </summary>
            /// <param name="lineToParse">The line to parse.</param>
            public ArgumentParser(string lineToParse)
            {
                var lineToParseElements = lineToParse.Split(' ');
                Keyword = lineToParseElements[0];

                for (int i = 1; i < lineToParseElements.Length; i++)
                {
                    int argument = 0;
                    if (int.TryParse(lineToParseElements[i], out argument))
                    {
                        arguments.Add(argument);
                    }
                }
            }

            /// <summary>
            /// Gets the keyword.
            /// </summary>
            /// <value>The keyword.</value>
            public string Keyword { get; private set; }

            /// <summary>
            /// Gets the specific argument (starting from zero). Ignores
            /// arguments that aren't integers.
            /// </summary>
            /// <param name="argumentNumber">The (zero-based) argument number.</param>
            /// <returns>The argument.</returns>
            public int Argument(int argumentNumber)
            {
                if (argumentNumber > arguments.Count - 1)
                {
                    throw new InvalidOperationException(string.Format(
                        "Keyword {0} requires {1} arguments, but {2} only present in the file.",
                        Keyword,
                        argumentNumber,
                        arguments.Count));
                }

                return arguments[argumentNumber];
            }
        }
    }
}
